﻿using System;
using System.Linq;
using GeneticAlgorithms.Alphabets;
using GeneticAlgorithms.Factories;
using GeneticAlgorithms.Genomes;
using GeneticAlgorithms.Populations;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using GeneticAlgorithms.Operators.Fitness;

namespace GeneticAlgorithms.Tests
{
    
    
    /// <summary>
    ///This is a test class for FixedSizePopulationTest and is intended
    ///to contain all FixedSizePopulationTest Unit Tests
    ///</summary>
    [TestClass()]
    public class FixedSizePopulationTest : PopulationBaseTest
    {
        //private TestContext testContextInstance;
        /*
        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }
        */
        #region Additional test attributes
        // 
        //You can use the following additional attributes as you write your tests:
        //
        //Use ClassInitialize to run code before running the first test in the class
        //[ClassInitialize()]
        //public static void MyClassInitialize(TestContext testContext)
        //{
        //}
        //
        //Use ClassCleanup to run code after all tests in a class have run
        //[ClassCleanup()]
        //public static void MyClassCleanup()
        //{
        //}
        //
        //Use TestInitialize to run code before running each test
        //[TestInitialize()]
        //public void MyTestInitialize()
        //{
        //}
        //
        //Use TestCleanup to run code after each test has run
        //[TestCleanup()]
        //public void MyTestCleanup()
        //{
        //}
        //
        #endregion

        [TestMethod()]
        [DeploymentItem("GeneticAlgorithms.dll")]
        public override void MergeTest()
        {
            // this simple function just counts the number of "true"s on the genome
            Func<ArrayGenome<bool>, double> calcFitness = genome => genome.Count(gene => gene);
            
            int populationSize = 10;
            int geneLength = 40;
            CloneableGenomeFactory<ArrayGenome<bool>> factory = 
                new CloneableGenomeFactory<ArrayGenome<bool>>(
                    new ArrayGenome<bool>(new BinaryAlphabet(), geneLength));

            FixedSizePopulation_Accessor<ArrayGenome<bool>, bool> target =
                new FixedSizePopulation_Accessor<ArrayGenome<bool>, bool>(
                    populationSize,
                    factory,
                    calcFitness);

            target.EliteSize = populationSize / 2;
            Assert.AreEqual(populationSize, target.Count);

            Random rnd = new Random();

            double[] fitnessValues = new double[populationSize];
            fitnessValues.SetValues(d => 100 + rnd.NextDouble() * 100);
            ArrayGenome<bool>[] children = new ArrayGenome<bool>[populationSize];
            children.SetValues(g => new ArrayGenome<bool>(new BinaryAlphabet(), geneLength));
            children.ForEach((g, i) => g.Fitness = ((i % 2) * 200) + rnd.NextDouble() * 100);
            for (int i = 0; i < target.Count; ++i)
            {
                var g = new ArrayGenome<bool>(new BinaryAlphabet(), geneLength);
                g.Fitness = fitnessValues[i];
                target[i] = g;
            }

            Array.Sort<double>(fitnessValues, (x, y) => Math.Sign(y - x));
            target.Merge(children);
            //Array.Sort<ArrayGenome<bool>>(children, (c1, c2) => c1.Fitness.Value.CompareTo(c2.Fitness.Value));
            var orderedChildrn = children.OrderByDescending(g => g.Fitness.Value);
            for (int i = 0; i < target.EliteSize; ++i)
            {
                Assert.IsTrue(target.Any(g => g.Fitness == fitnessValues[i]), 
                    "An element of fitness {0} is missing among the resulting genomes",
                    fitnessValues[i]);
                Assert.IsTrue(target.Any(g => g == children[i]));
            }
        }

        [TestMethod()]
        [DeploymentItem("GeneticAlgorithms.dll")]
        public void GetEliteTest()
        {
            // this simple function just counts the number of "true"s on the genome
            Func<ArrayGenome<bool>, double> calcFitness = genome => genome.Count(gene => gene);
            int populationSize = 10;
            int geneLength = 40;
            CloneableGenomeFactory<ArrayGenome<bool>> factory =
                new CloneableGenomeFactory<ArrayGenome<bool>>(
                    new ArrayGenome<bool>(new BinaryAlphabet(), geneLength));

            FixedSizePopulation_Accessor<ArrayGenome<bool>, bool> target =
                new FixedSizePopulation_Accessor<ArrayGenome<bool>, bool>(
                    populationSize,
                    factory,
                    calcFitness);

            target.EliteSize = populationSize / 2;
            Assert.AreEqual(populationSize, target.Count);

            Random rnd = new Random();

            double[] fitnessValues = new double[populationSize];
            fitnessValues.SetValues((d) => rnd.NextDouble() * 100);

            for (int i = 0; i < target.Count; ++i)
            {
                var g = new ArrayGenome<bool>(new BinaryAlphabet(), geneLength);
                g.Fitness = fitnessValues[i];
                target[i] = g;
            }

            Array.Sort<double>(fitnessValues, (x, y) => Math.Sign(y - x));
            var elite = target.GetElite();
            for (int i = 0; i < target.EliteSize; ++i)
            {
                Assert.IsTrue(elite.Any(g => g.Fitness == fitnessValues[i]), 
                    "An element of fitness {0} is missing among the elite genomes",
                    fitnessValues[i]);
            }
        }

        protected override PopulationBase<ArrayGenome<TGene>, TGene> CreatePopulationBase<TGene>(int populationSize)
        {
            return CreatePopulation<TGene>(populationSize);
        }

        private FixedSizePopulation<ArrayGenome<TGene>, TGene> CreatePopulation<TGene>(int populationSize)
        {
            string geneType = typeof(TGene).Name;
            
            switch (geneType)
            {
                case "Int32":
                    return (FixedSizePopulation<ArrayGenome<TGene>, TGene>) 
                        Activator.CreateInstance(
                            typeof(FixedSizePopulation<ArrayGenome<int>, int>),
                            populationSize,
                            new CloneableGenomeFactory<ArrayGenome<int>>(new ArrayGenome<int>(new IntAlphabet(), 10)),
                            new DummyFitnessProvider<int>());
                case "Boolean":
                    return (FixedSizePopulation<ArrayGenome<TGene>, TGene>)
                        Activator.CreateInstance(
                            typeof(FixedSizePopulation<ArrayGenome<bool>, bool>),
                            populationSize,
                            new CloneableGenomeFactory<ArrayGenome<bool>>(new ArrayGenome<bool>(new BinaryAlphabet(), 10)),
                            new DummyFitnessProvider<bool>());
            }
            Assert.Inconclusive("Don't know how to create populations with gene type {0}.", geneType);
            return null;
        }

        private class DummyFitnessProvider<T> : IFitnessProvider<ArrayGenome<T>, T>
        {
            #region IFitnessProvider<ArrayGenome<T>,T> Members

            public double CalculateFitness(ArrayGenome<T> genome)
            {
                return 0;
            }

            #endregion
        }
    }
}
